package scalax.collection.constrained
package constraints

import language.{higherKinds, postfixOps}
import collection.Set
import collection.mutable.{Set => MutableSet}

import scalax.collection.GraphPredef._,
       scalax.collection.GraphEdge._
import scalax.collection.{Graph => SimpleGraph}

import PreCheckFollowUp._

/** Ensures that the underlying `Graph` is acyclic at any time. */
class Acyclic[N, E[X] <: EdgeLikeIn[X]] (override val self: Graph[N,E])
  extends Constraint[N,E] (self)
  with    ConstraintHandlerMethods[N,E]
{
  override def preCreate(nodes: collection.Iterable[N],
                         edges: collection.Iterable[E[N]]) = PreCheckResult(PostCheck)
  /** Adding a single node cannot produce a cycle. */
  override def preAdd(node: N) = PreCheckResult(Complete)
  /**
   * When inserting an edge with a source contained in the graph
   * a cycle is produced if there exists a target node of this edge such that
   * there is a path from the target node to the source node.  
   */
  def preAdd(edge: E[N]) = PreCheckResult.complete(
    edge.nonLooping &&
    { val isUndirected = edge.isUndirected
      val checkFrom, checkTo = MutableSet.empty[self.NodeT]
      def find(n: N) = self find n
      def add1(n: N) {
        find(n) map { inner =>
          checkFrom += inner
          if (isUndirected) checkTo += inner
        }
      }
      def add2(toSet: MutableSet[self.NodeT], n: N, nUndi: N) {
        find(n)     map (inner => toSet += inner)
        find(nUndi) map {inner => if(isUndirected) toSet += inner }
      }
      add2(checkTo,   edge._1, edge._2)
      add2(checkFrom, edge._2, edge._1)
      for (n <- edge.iterator.drop(2))
        add1(n)

      ! ((for (from <- checkFrom; to <- checkTo) yield (from, to)
         ) exists (t => t._1 hasSuccessor t._2))
    }
  )
  /** This class makes it possible to carry over nodes that are passed to a pre-check and
   *  are already contained in the graph to `postAdd`. Thus we can limit the number of start
   *  nodes for cycle detection in the post-check to these passed over docking nodes.
   */
  protected class Result (followUp:         PreCheckFollowUp,
                          val dockingNodes: Set[self.NodeT])
    extends PreCheckResult(followUp)

  protected object Result extends PreCheckResultCompanion {
    def apply(followUp: PreCheckFollowUp) = new Result(followUp, Set.empty[self.NodeT])
    def apply(followUp:     PreCheckFollowUp,
              nodesToCheck: Set[self.NodeT] ) = new Result(followUp, nodesToCheck)
    def unapply(result: Result): Option[Set[self.NodeT]] = Some(result.dockingNodes)
  }

  /** If `elems` is relatively small, preventively ensure that there is no cycle
   *  within the new elements.
   */
  override def preAdd(elems: InParam[N,E]*) = 
    if (elems.size * 10 < self.size) {
      val p: Param.Partitions[N,E] = elems
      val graphAdd = SimpleGraph.from(
          p.toOuterNodes, p.toOuterEdges)(
          self.edgeT, self.config.asInstanceOf[SimpleGraph.Config])
      if (graphAdd.isCyclic)
        PreCheckResult(Abort)
      else {
        val found = (for (n <- p.toOuterNodes) yield self find n) filter (_.isDefined)
        val dockingNodes = Set()
        Result(PostCheck, (found map (_.get)) toSet)
      }
    } else PreCheckResult(PostCheck)

  override def postAdd (newGraph: Graph[N,E],
                        passedNodes: Iterable[N],
                        passedEdges: Iterable[E[N]],
                        preCheck   : PreCheckResult) = preCheck match {
      case Result(docking) => ! (docking exists (_.findCycle.isDefined)) 
      case _               => newGraph.isAcyclic 
    }

  override def preSubtract(node: self.NodeT, forced: Boolean) = PreCheckResult(Complete)
  override def preSubtract(edge: self.EdgeT, forced: Boolean) = PreCheckResult(Complete)
  override def preSubtract(nodes: => Set[self.NodeT],
                           edges: => Set[self.EdgeT],
                           simple:   Boolean)                 = PreCheckResult(Complete)
}
object Acyclic extends ConstraintCompanion[Acyclic] {
  def apply [N, E[X] <: EdgeLikeIn[X]] (self: Graph[N,E]) = new Acyclic[N,E] (self)
}
